home *** CD-ROM | disk | FTP | other *** search
/ Stone Design / Stone Design.iso / Stone_Friends / NeXT-Icons / next-icon@gun.com / Apps / ImagePortfolio / apputils.subproj / ScrollText.m < prev   
Encoding:
Text File  |  1993-06-03  |  19.4 KB  |  744 lines

  1. // -------------------------------------------------------------------------------------
  2. // ScrollText.m
  3. // author: Martin D. Flynn
  4. // (see ScrollText.h for usage information)
  5. // -------------------------------------------------------------------------------------
  6. // Permission is granted to freely redistribute this source code, and to use fragments
  7. // of this code in your own applications if you find them to be useful.  This class,
  8. // along with the source code, come with no warranty of any kind, and the user assumes
  9. // all responsibility for its use.
  10. // -------------------------------------------------------------------------------------
  11.  
  12. #import <appkit/appkit.h>
  13. #import <libc.h>
  14. #import <mach/cthreads.h>
  15. #import <stdlib.h>
  16. #import <stdarg.h>
  17. #import <string.h>
  18. #import <pwd.h>
  19. #import <sys/types.h>
  20. #import <sys/wait.h>
  21. #import "ScrollText.h"
  22.  
  23. // -------------------------------------------------------------------------------------
  24. // mutex defines
  25. #define    mutexAlloc        mutex_alloc
  26. #define    mutexFree        mutex_free
  27. #define    mutexLock        mutex_lock
  28. #define    mutexUnlock        mutex_unlock
  29.  
  30. // -------------------------------------------------------------------------------------
  31. // null text attribute structure
  32. static NXColor            nullColor = { 0 };
  33. static textAttr_t        nullAttr = { (id)nil, 0 };
  34. #define    isNullAttr(X)    (!X.fontId && !X.colorMode)
  35.  
  36. // -------------------------------------------------------------------------------------
  37. // main thread handle
  38. #define    isMainTHREAD    (mainThread == cthread_self())
  39. static cthread_t        mainThread = (cthread_t)nil;
  40.  
  41. // -------------------------------------------------------------------------------------
  42. // Should be proto-typed elsewhere
  43. @interface Object(ShouldBeElsewhere)
  44. - (const char*)printerName;    // should be in PrintInfo
  45. @end
  46.  
  47. // -------------------------------------------------------------------------------------
  48. // Object thread support
  49. @interface Object(ThreadPerform)
  50. - mainThreadPerform:(SEL)aSelector with:anArg wait:(BOOL)wait;
  51. @end
  52.  
  53. // -------------------------------------------------------------------------------------
  54. // Object notified when command completes
  55. @interface Object(ScrollTextDelegate)
  56. - commandDidComplete:shellId withError:(int)errorCode;
  57. @end
  58.  
  59. // -------------------------------------------------------------------------------------
  60. // private methods
  61. @interface ScrollText(Private)
  62. - _setTextAttrFont:fontId color:(int)mode:(NXColor)color;
  63. - (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr;
  64. - _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr;
  65. - _appendTextToQueue:(const char*)buffer attr:(textAttr_t)tAttr;
  66. - _appendQueue:sender;
  67. - _delayedAppendQueue;
  68. - (int)_queueLength:(textQueue_t*)queue;
  69. - (int)_maxRunLength:(textQueue_t*)queue;
  70. - _stopCommand;
  71. - (int)_popen:(const char*)cmd cmdPath:(const char*)cmdPath;
  72. - (int)_pclose;
  73. - (void)_gotData;
  74. @end
  75.  
  76. // -------------------------------------------------------------------------------------
  77. /* convert color to gray */
  78. static float cvtColor2Gray(NXColor color)
  79. {
  80.     float    gray;
  81.     NXConvertColorToGray(color, &gray);
  82.     return gray;
  83. }
  84.  
  85. // -------------------------------------------------------------------------------------
  86. @implementation ScrollText
  87.  
  88. // -------------------------------------------------------------------------------------
  89. /* initializes the outlet (to be executed by main thread only!) */
  90. + newScrollText:anObject
  91. {
  92.  
  93.     /* check thread */
  94.     if (!mainThread) mainThread = cthread_self();
  95.     
  96.     /* init */
  97.     self             = [super new];
  98.     scrollView         = anObject;
  99.     textView         = [scrollView docView];
  100.     wasEditable         = [textView isEditable];
  101.     autoLf             = NO;
  102.     queueMutex         = mutexAlloc();
  103.     queueData         = (textQueue_t*)nil;
  104.     queueBack         = (textQueue_t*)nil;
  105.     runAttr             = nullAttr;
  106.     cmdChild         = 0;
  107.     
  108.     /* set textView attributes */
  109.     //[textView setEditable:NO];
  110.     //[textView setMonoFont:NO];
  111.     
  112.     return self;
  113. }
  114.  
  115. /* return textView id (docView) */
  116. - docView
  117. {
  118.     return textView;
  119. }
  120.  
  121. /* return scroll view */
  122. - scrollView
  123. {
  124.     return scrollView;
  125. }
  126.  
  127. /* set delegate */
  128. - setDelegate:theDelegate
  129. {
  130.     delegate = theDelegate;
  131.     return self;
  132. }
  133.  
  134. /* set auto linefeed mode */
  135. - setAutoLineFeed:(BOOL)mode
  136. {
  137.     autoLf = mode;
  138.     return self;
  139. }
  140.  
  141. /* free object */
  142. - free
  143. {
  144.  
  145.     /* kill any running child process */
  146.     [self killCommand];
  147.  
  148.     /* free queueData */
  149.     for (;queueData;) {
  150.         textQueue_t    *next = queueData->next;
  151.         if (queueData->record) free(queueData->record);
  152.         free(queueData);
  153.         queueData = next;
  154.     }
  155.     mutexFree(queueMutex);
  156.     
  157.     /* free super */
  158.     return [super free];
  159.     
  160. }
  161.  
  162. // --------------------------------------------------------------------------------
  163.  
  164. /* set font */
  165. - _setTextAttrFont:fontId color:(int)mode:(NXColor)color
  166. {
  167.     textAttr_t    tAttr = nullAttr;
  168.     
  169.     /* init text attributes */
  170.     tAttr.fontId    = fontId;
  171.     tAttr.colorMode    = mode;
  172.     tAttr.color        = color;
  173.     [self _appendTextAndMakeVisible:(char*)nil attr:tAttr];
  174.     
  175.     return self;
  176. }
  177.  
  178. /* set font */
  179. - setTextAttributeFont:fontId
  180. {
  181.     return [self _setTextAttrFont:fontId color:0:nullColor];
  182. }
  183.  
  184. /* set gray */
  185. - setTextAttributeGray:(float)aGray
  186. {
  187.     return [self _setTextAttrFont:(id)nil color:1:NXConvertGrayToColor(aGray)];
  188. }
  189.  
  190. /* set gray */
  191. - setTextAttributeColor:(NXColor)aColor
  192. {
  193.     return [self _setTextAttrFont:(id)nil color:2:aColor];
  194. }
  195.  
  196. /* set default tabs */
  197. // THIS NEEDS TO BE REDONE
  198. - setTabStops:(float*)tabArray count:(int)c
  199. {
  200.     NXTextStyle    style = *((NXTextStyle*)[textView defaultParaStyle]);
  201.     style.numTabs = (short)c;
  202.     style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs);
  203.     while (--c >= 0) { style.tabs[c].kind = NX_LEFTTAB; style.tabs[c].x = tabArray[c]; }
  204.     [textView setParaStyle:(void*)&style];
  205.     return self;
  206. }
  207.  
  208. /* repeat given tab multiple times */
  209. - setTab:(float)tabSize count:(int)c
  210. {
  211.     int            i;
  212.     NXTextStyle    style = *((NXTextStyle*)[textView defaultParaStyle]);
  213.     style.numTabs = (short)c;
  214.     style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs);
  215.     for (i = 0; i < c; i++) {
  216.         style.tabs[i].kind = NX_LEFTTAB;
  217.         style.tabs[i].x = (float)(i + 1) * tabSize;
  218.     }
  219.     [textView setParaStyle:(void*)&style];
  220.     return self;
  221. }
  222.  
  223. // --------------------------------------------------------------------------------
  224.  
  225. /* clear text scroll view area */
  226. - clearScrollText
  227. {
  228.     [textView setEditable:YES];
  229.     [textView setText:""];
  230.     if (!wasEditable) [textView setEditable:NO];
  231.     [scrollView display];
  232.     return self;
  233. }
  234. - clear:sender
  235. {
  236.     return [self clearScrollText];
  237. }
  238.  
  239. /* delete lines */
  240. - deleteLinesFrom:(int)fLine to:(int)tLine
  241. {
  242.     [textView setEditable:YES];
  243.     [textView setSel:[textView positionFromLine:fLine]:[textView positionFromLine:tLine+1]];
  244.     [textView replaceSel:""];
  245.     if (!wasEditable) [textView setEditable:NO];
  246.     [scrollView display];
  247.     return self;
  248. }
  249.  
  250. /* return current number of lines */
  251. - (int)textLines
  252. {
  253.     return [textView lineFromPosition:[textView textLength]];
  254. }
  255.     
  256. // --------------------------------------------------------------------------------
  257. // append text to scroll view
  258.  
  259. /* append buffer to view: return YES if text was visible */
  260. - (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr
  261. {
  262.     int        txtLen;
  263.     NXSelPt    startPt, endPt;
  264.     NXRect    rect;
  265.     
  266.     /* check for font/gray change (save state) */
  267.     if (tAttr.fontId) {
  268.         runAttr.fontId = tAttr.fontId;
  269.     }
  270.     if (tAttr.colorMode) {
  271.         runAttr.colorMode = tAttr.colorMode;
  272.         runAttr.color = tAttr.color;
  273.     }
  274.     if (!buffer || !*buffer || (len == 0)) return NO;
  275.     
  276.     /* get ready to print text */
  277.     [textView getVisibleRect:&rect];                      // visible rectangle
  278.     [textView setEditable:YES];
  279.     txtLen = [textView textLength];
  280.     [textView setSel:txtLen :txtLen];
  281.     [textView getSel:&startPt :&endPt];                   // selected coordinates
  282.  
  283.     /* set text run attributes if specified */
  284.     if (!isNullAttr(runAttr)) {
  285.         if ([textView isMonoFont]) [textView setMonoFont:NO];
  286.         if (!txtLen) { [textView replaceSel:" "]; [textView setSel:0 :1]; }
  287.         if (runAttr.fontId) [textView setSelFont:runAttr.fontId];
  288.         if (runAttr.colorMode == 1) [textView setSelGray:cvtColor2Gray(runAttr.color)]; else
  289.         if (runAttr.colorMode == 2) [textView setSelColor:runAttr.color];
  290.         runAttr = nullAttr;
  291.     }
  292.     
  293.     /* print text */
  294.     if (len > 0) [textView replaceSel:buffer length:len];
  295.     else [textView replaceSel:buffer];
  296.     if (!wasEditable) [textView setEditable:NO];
  297.     
  298.     return (rect.origin.y + rect.size.height > endPt.y);  // was visible?
  299. }
  300.  
  301. /* append text to view and scroll to visible */
  302. - _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr
  303. {
  304.     BOOL    wasVisible;
  305.   
  306.     /* if not main thread, append buffer to queue */
  307.     if (!isMainTHREAD) return [self _appendTextToQueue:buffer attr:tAttr];
  308.  
  309.     /* print queue contents */
  310.     [self _appendQueue:self];
  311.   
  312.     /* print buffer */
  313.     wasVisible = [self _appendTextToView:buffer len:-1 attr:tAttr];
  314.     if (autoLf && buffer) [self _appendTextToView:"\n" len:-1 attr:nullAttr];
  315.     if (wasVisible) [textView scrollSelToVisible];
  316.   
  317.     return self;
  318. }
  319.     
  320. // --------------------------------------------------------------------------------
  321. // queue up text to print / print text queue
  322.  
  323. /* return queue length */
  324. - (int)_queueLength:(textQueue_t*)queue
  325. {
  326.     int            len;
  327.     textQueue_t    *qPtr;
  328.     qPtr = (queue)? queue: queueData;
  329.     for (len = 0; qPtr; qPtr = qPtr->next) {
  330.         if (qPtr->record) len += strlen(qPtr->record) + 1;
  331.     }
  332.     return len;
  333. }
  334.  
  335. /* return queue length */
  336. - (int)_maxRunLength:(textQueue_t*)queue
  337. {
  338.     int            len, maxLen;
  339.     textQueue_t    *qPtr;
  340.     qPtr = (queue)? queue: queueData;
  341.     for (maxLen = len = 0; qPtr; qPtr = qPtr->next) {
  342.         if (!isNullAttr(qPtr->attr)) {
  343.             if (len > maxLen) maxLen = len;
  344.             len = 0;
  345.         }
  346.         if (qPtr->record) len += strlen(qPtr->record) + 1;
  347.     }
  348.     return MAX(maxLen, len);
  349. }
  350.  
  351. /* delayed append queue */
  352. - _delayedAppendQueue
  353. {
  354.     if ([self respondsTo:@selector(mainThreadPerform:with:wait:)])
  355.         [self mainThreadPerform:@selector(_appendQueue:) with:self wait:NO];
  356.     return self;
  357. }
  358.  
  359. /* add text string to queue (to be executed by non-main thread only) */
  360. - _appendTextToQueue:(const char*)buffer attr:(textAttr_t)tAttr
  361. {
  362.     textQueue_t    *newData;
  363.  
  364.     /* load temporarily permanent buffer */
  365.     newData         = (textQueue_t*)malloc(sizeof(textQueue_t));
  366.     newData->record = buffer? NXCopyStringBuffer(buffer) : (char*)nil;
  367.     newData->attr    = tAttr;
  368.     newData->next   = (textQueue_t*)nil;
  369.   
  370.     /* lock the queue */
  371.     mutexLock(queueMutex);
  372.   
  373.     /* add it to the queue */
  374.     if (!queueData) { queueData = newData; [self _delayedAppendQueue]; }
  375.     else queueBack->next = newData;
  376.     queueBack = newData;
  377.  
  378.     /* unlock the queue */
  379.     mutexUnlock(queueMutex);
  380.   
  381.     return self;
  382. }
  383.  
  384. /* print text queue contents (executed by main thread only!) */
  385. // needs to be fixed to support fonts/grays
  386. - _appendQueue:sender
  387. {
  388.     int            i;
  389.     char        *text;
  390.     textQueue_t    *queue, *next;
  391.   
  392.     /* get pointer to queue list */
  393.     mutexLock(queueMutex);
  394.     queue = queueData;
  395.      queueData = queueBack = (textQueue_t*)nil;
  396.     mutexUnlock(queueMutex);
  397.     if (!queue) return self;
  398.   
  399.     /* concatenate text */
  400.     text = (char*)malloc([self _maxRunLength:queue] + 1);
  401.     for (i = 0; queue;) {
  402.     
  403.         /* check for attribute change */
  404.         if (!isNullAttr(queue->attr)) {    // print contents of 'text'
  405.             if ((i > 0) && [self _appendTextToView:text len:-1 attr:nullAttr])
  406.                 [textView scrollSelToVisible];
  407.             [self _appendTextToView:"" len:0 attr:queue->attr];
  408.             text[i = 0] = 0;
  409.         }
  410.         
  411.         /* append record to string buffer */
  412.         if (queue->record) {
  413.             i += sprintf(&text[i], "%s%s", queue->record, ((autoLf)? "\n": ""));
  414.             free(queue->record);
  415.         }
  416.         
  417.         /* next node */
  418.         next = queue->next;
  419.         free(queue);
  420.         queue = next;
  421.         
  422.     }
  423.   
  424.     /* place remaining text into scroll view */
  425.     if ([self _appendTextToView:text len:-1 attr:nullAttr]) [textView scrollSelToVisible];
  426.     free(text);
  427.     
  428.     return self;
  429. }
  430.  
  431. // --------------------------------------------------------------------------------
  432. // append a formatted text string message into a text view
  433. // input from ktaylor
  434.  
  435. /* append text unformatted */
  436. - (int)textPrint:(const char*)buffer
  437. {
  438.     [self _appendTextAndMakeVisible:buffer attr:nullAttr];
  439.     return strlen(buffer);
  440. }
  441.  
  442. /* append text with variable args into textView */
  443. - (int)textPrintf:(const char*)fmt args:(va_list)args
  444. {
  445.     char    tempString[textStringSIZE] = { 0 };
  446.     int        retVal = vsprintf(tempString, fmt, args);
  447.     [self _appendTextAndMakeVisible:tempString attr:nullAttr];
  448.     return retVal;
  449. }
  450.  
  451. /* append text with variable args into textView */
  452. - (int)textPrintf:(const char*)fmt, ...
  453. {
  454.     va_list        args;
  455.     int            retVal;
  456.     va_start(args, fmt);
  457.     retVal = [self textPrintf:fmt args:args];     
  458.     va_end(args);
  459.     return retVal;
  460. }
  461.  
  462. /* append text with fprintf(...) style format */
  463. int textPrintf(id self, const char *fmt, ...)
  464. {
  465.     va_list        args;
  466.     int            retVal;
  467.     va_start(args, fmt);
  468.     retVal = [self textPrintf:fmt args:args]; 
  469.     va_end(args);
  470.     return retVal;
  471. }
  472.  
  473. // --------------------------------------------------------------------------------
  474. // view printing
  475.  
  476. /* prints the textView */
  477. - print:sender
  478. {
  479.     id        oldInfo, pInfo = [PrintInfo new];
  480.     [pInfo setVertCentered:NO];
  481.     oldInfo = [NXApp setPrintInfo:pInfo];
  482.     [textView lockFocus];
  483.     [textView printPSCode:self];
  484.     [textView unlockFocus];
  485.     [NXApp setPrintInfo:oldInfo];
  486.     [pInfo free];
  487.     return self;
  488. }
  489.  
  490. /* print textView using enscript */
  491. #define            tempFILE        ".textPrint"
  492. #define            previewFILE     ".previewPrint.ps"
  493. #define            CAT(S)          strcat(command, (S));
  494. - enscriptPrint:(char*)title option:(char*)option printPanel:(BOOL)prtPnl
  495. {
  496.     NXStream    *stream;
  497.     char        temp[256], command[512], *panelStatus;
  498.     int            rtn;
  499.     id            oldInfo;
  500.     id            printPanel = (id)nil;
  501.     id            printInfo = (id)nil;
  502.   
  503.     /* create temp objects */
  504.     printInfo = [PrintInfo new];
  505.     oldInfo = [NXApp setPrintInfo:printInfo];
  506.   
  507.     /* run print panel */
  508.     if (prtPnl)   { printPanel = [PrintPanel new]; rtn = [printPanel runModal]; }
  509.     else          { printPanel = (id)nil;          rtn = NX_OKTAG; }
  510.   
  511.     /* results of print panel */
  512.     if (rtn != NX_CANCELTAG) {
  513.   
  514.         /* save file to disk */
  515.         stream = NXOpenMemory((char*)nil, 0, NX_WRITEONLY);
  516.         [textView writeText:stream];
  517.         NXSaveToFile(stream, tempFILE);
  518.         NXClose(stream);
  519.  
  520.         /* init default options */
  521.         sprintf(command, "enscript -qhG%d", [printInfo pagesPerSheet]);
  522.         CAT(([printInfo orientation] == NX_LANDSCAPE)? "r": "R");
  523.     
  524.         /* printer option */
  525.         if (rtn == NX_OKTAG) {        // print
  526.             sprintf(temp, " -#%d", [printInfo copies]); CAT(temp);     // # copies
  527.             sprintf(temp, " -P%s", [printInfo printerName]); CAT(temp);// printer name
  528.             panelStatus = "Printing...";
  529.         } else
  530.         if (rtn == NX_SAVETAG) {    // save to file
  531.             sprintf(temp, " -p%s", [printInfo outputFile]); CAT(temp);
  532.             panelStatus = "Saving...";
  533.         } else
  534.         if (rtn == NX_PREVIEWTAG) {    // prieview
  535.             CAT(" -p" previewFILE);
  536.             panelStatus = "Messaging Preview...";
  537.         }
  538.  
  539.         /* font */
  540.         sprintf(temp, " -f%s%d", [[textView font] name], (int)[[textView font] pointSize]);
  541.         CAT(temp);
  542.  
  543.         /* options */
  544.         if (title) {sprintf(temp, " -b\"%s\"", title); CAT(temp);}  // title
  545.         if (option) { CAT(" "); CAT(option); }                      // other options
  546.         CAT(" "); CAT(tempFILE);                                    // file name
  547.   
  548.         /* execute print command */
  549. //        if (printPanel) [printPanel->status setStringValue:panelStatus];
  550.         system(command);
  551.     
  552.         /* preview check */
  553.         if (rtn == NX_PREVIEWTAG) system("open " previewFILE);
  554.  
  555.     }
  556.  
  557.     /* restore state and free objects */
  558.     [NXApp setPrintInfo:oldInfo];
  559.     [printInfo free];
  560.     if (printPanel) [printPanel orderOut:(id)nil];
  561.   
  562.     return self;
  563. }
  564.   
  565. // --------------------------------------------------------------------------------
  566. // executing commands within a shell, using the scroll text as output
  567.  
  568. /* stop command */
  569. - _stopCommand
  570. {
  571.     int    error;
  572.     DPSRemoveFD(inputDescriptor);
  573.     error = [self _pclose];
  574.     close(inputDescriptor);
  575.     [self commandDidCompleteWithError:error];
  576.     return self;
  577. }
  578.  
  579. /* set environment variable */
  580. static int _setenv(char **ep, char *eVal, char *fmt, ...)
  581. {
  582.     va_list            args;
  583.     register char    *cp, *dp;
  584.     va_start(args, fmt);
  585.     vsprintf(eVal, fmt, args);
  586.     va_end(args);
  587.     for (;dp = *ep; ep++) {
  588.         for (cp = eVal; (*cp == *dp) && (*cp != '=') && *cp; cp++, dp++) continue;
  589.         if (((*cp == '=') || !*cp) && ((*dp == '=') || !*dp)) { *ep = eVal; return 0; }
  590.     }
  591.     return -1;
  592. }
  593.  
  594. /* open pipe to shell and execute command */
  595. - (int)_popen:(const char*)cmd cmdPath:(const char*)cmdPath
  596. {
  597.     int            inputP[2], hisOutput, myInput;
  598.     const char    **locEnv = environ;
  599.     
  600.     /* open pipe */
  601.     pipe(inputP);
  602.     myInput = inputP[0];
  603.     hisOutput = inputP[1];
  604.     
  605.     /* fork and execute shell */
  606.     if ((cmdChild = vfork()) == 0) {
  607.         int i;
  608.         char **env, *path, *ePath;
  609.         
  610.         /* make local copy of environment table */
  611.         for (i = 0; locEnv[i]; i++);
  612.         env = (char**)alloca(sizeof(char*) * (i + 1));            // allocate on stack
  613.         memcpy(env, locEnv, sizeof(char*) * (i + 1));
  614.         
  615.         /* check for additional path requirements */
  616.         if (cmdPath && (ePath = getenv("PATH"))) {
  617.             path = (char*)alloca(strlen(ePath) + strlen(cmdPath) + 8);
  618.             _setenv(env, path, "PATH=%s:%s", cmdPath, ePath);
  619.         }
  620.         
  621.         /* set up pipe handles */
  622.         close(myInput);
  623.         if (hisOutput != 1) dup2(hisOutput, 1);
  624.         if (hisOutput != 2) dup2(hisOutput, 2);
  625.         if ((hisOutput != 1) && (hisOutput != 2)) close(hisOutput);
  626.         
  627.         /* execute command */
  628.         setpgrp(0, getpid());
  629.         execle("/bin/csh", "csh", "-f", "-c", cmd, (char *)nil, env);
  630.         _exit(RUNCMD_EXEC);
  631.         
  632.     }
  633.     
  634.     /* set io */
  635.     if (cmdChild == -1)  { close(myInput); myInput = -1; }
  636.     close(hisOutput);
  637.     
  638.     return myInput;
  639. }
  640.  
  641. /* close shell command pipe */
  642. - (int)_pclose
  643. {
  644.     int            pid, omask, err;
  645.     union wait    status;
  646.     omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
  647.     while ((pid = wait(&status)) != cmdChild && pid != -1);
  648.     (void)sigsetmask(omask);
  649.     return (err = (status.w_status & 0xFF))? -1 : (status.w_status >> 8) & 0xFF;
  650. }
  651.  
  652. /* filter for data piped from command shell */
  653. - (void)_gotData
  654. {
  655.     char    data[1024];
  656.     int        n, cnt = 0;
  657.     BOOL    doScroll = NO;
  658.  
  659.     /* read available text */
  660.     do {
  661.         if ((n = read(inputDescriptor, data, sizeof(data))) >= 0) {
  662.             cnt += n;
  663.             if (n && [self _appendTextToView:data len:n attr:nullAttr]) doScroll = YES;
  664.             if (delegate && [delegate respondsTo:@selector(runCommandOutput:len:)]) {
  665.                 [delegate perform:@selector(runCommandOutput:len:) with:(id)data with:(id)n];
  666.             }
  667.         }
  668.     } while (n == sizeof(data));
  669.  
  670.     /* show text */
  671.     if (doScroll) [textView scrollSelToVisible];
  672.     if (!cnt) [self _stopCommand];
  673.     
  674. }
  675.  
  676. /* fd routine for receiving data */
  677. static void gotData(int fd, void *self)
  678. {
  679.     [(ScrollText*)self _gotData];
  680. }
  681.  
  682. /* execute a command */
  683. - runCommand:(const char*)command withPath:(const char*)cmdPath
  684. {
  685.     inputDescriptor = [self _popen:command cmdPath:cmdPath];
  686.     if (inputDescriptor >= 0) {
  687.         DPSAddFD(inputDescriptor, gotData, self, NX_BASETHRESHOLD);
  688.         return self;
  689.     }
  690.     return (id)nil;
  691. }
  692.  
  693. /* execute a command */
  694. - runCommand:(const char*)command
  695. {
  696.     return [self runCommand:command withPath:(char*)nil];
  697. }
  698.  
  699. /* terminate command */
  700. - terminateCommand
  701. {
  702.     if (cmdChild > 0) {
  703.         killpg(cmdChild, SIGTERM);
  704.         kill(cmdChild, SIGTERM);
  705.     }
  706.     return self;
  707. }
  708.   
  709. /* kill command */
  710. - killCommand
  711. {
  712.     if (cmdChild > 0) {
  713.         killpg(cmdChild, SIGKILL);
  714.         kill(cmdChild, SIGKILL);
  715.     }
  716.     return self;
  717. }
  718.  
  719. // --------------------------------------------------------------------------------
  720. // support for remote shell server output
  721.  
  722. /* copy text to scrollView (MAIN THREAD ONLY!) */
  723. - (oneway void)commandOutput:(const char*)buffer len:(int)len
  724. {
  725.     if (len && buffer) {
  726.         if (!isMainTHREAD) {
  727.             NXLogError("(ScrollText)commandOutput:len: Not main thread!");
  728.             return;
  729.         }
  730.         if ([self _appendTextToView:buffer len:len attr:nullAttr])
  731.             [textView scrollSelToVisible];
  732.         free((char*)buffer);
  733.     }
  734. }
  735.  
  736. /* indicate that the shell has completed */
  737. - (oneway void)commandDidCompleteWithError:(int)errorCode;
  738. {
  739.     if (delegate && [delegate respondsTo:@selector(commandDidComplete:withError:)])
  740.         [delegate commandDidComplete:self withError:errorCode];
  741. }
  742.  
  743. @end
  744.